package com.audaque.springboot.foshanupload.web.esdatademo.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.audaque.springboot.foshanupload.web.esdatademo.constant.MemberType;
import com.audaque.springboot.foshanupload.web.esdatademo.model.pojo.ChildCount;
import com.audaque.springboot.foshanupload.web.esdatademo.model.pojo.JoinFamily;
import com.audaque.springboot.foshanupload.web.esdatademo.model.pojo.JoinField;
import com.audaque.springboot.foshanupload.web.esdatademo.service.JoinFamilyService;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;

import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.join.aggregations.Children;
import org.elasticsearch.join.aggregations.JoinAggregationBuilders;
import org.elasticsearch.join.query.JoinQueryBuilders;
import org.elasticsearch.join.query.ParentIdQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.ScriptedMetric;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.IOException;
import java.util.*;

@Service
public class JoinFamilyServiceImpl implements JoinFamilyService {

    private static final Log log = LogFactory.getLog(JoinFamilyServiceImpl.class);

    @Autowired
    RestHighLevelClient restHighLevelClient;



    @Override
    public void creatIndex() throws Exception {
        GetIndexRequest index = new GetIndexRequest("my-index-join_family");
        boolean exists = restHighLevelClient.indices().exists(index, RequestOptions.DEFAULT);
        if(exists){
            // 1.删除已经存在的索引
            DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("my-index-join_family");
            restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
        }

        // 2.创建一个索引
        CreateIndexRequest blog = new CreateIndexRequest("my-index-join_family");
        // 3.配置 settings
        blog.settings(Settings.builder().put("index.number_of_shards", 3).put("index.number_of_replicas", 1));
        // 4.配置字段类型，字段类型可以通过 JSON 字符串、Map 以及 XContentBuilder 三种方式来构建
        // 4.1.json 字符串的方式
        blog.mapping("{\n" +
                "\t\t\"properties\":{\n" +
                "\t\t\t\"my_id\":{\n" +
                "\t\t\t\t\"type\":\"keyword\"\n" +
                "\t\t\t},\n" +
                "\t\t\t\"name\":{\n" +
                "\t\t\t  \"type\":\"keyword\"\n" +
                "\t\t\t},\n" +
                "\t\t\t\"level\":{\n" +
                "\t\t\t  \"type\":\"keyword\"\n" +
                "\t\t\t},\n" +
                "\t\t\t\"join_filed\":{  //1关联的字段名，可以随意取\n" +
                "\t\t\t\t\"type\":\"join\", //类型需要定义为join\n" +
                "\t\t\t\t\"relations\":{ //定义层级关系，grand_parent ->parent -> child\n" +
                "\t\t\t\t\t\"grand_parent\":\"parent\",  \n" +
                "\t\t\t\t\t\"parent\":\"child\"\n" +
                "\t\t\t\t}\n" +
                "\t\t\t}\n" +
                "\t\t}\n" +
                "\t}\n", XContentType.JSON);
        // 5.执行请求，创建索引
        restHighLevelClient.indices().create(blog, RequestOptions.DEFAULT);


    }








    @Override
    public String addFamilyMember(String parentId, String name, MemberType type) {
        String id = UUID.randomUUID().toString();
        log.debug(id);
        JoinFamily member = new JoinFamily();
        member.setName(name);
        member.setLevel(type.getLevel());
        member.setMy_id(id);
        JoinField joinField = new JoinField();
        joinField.setCurrentType(type.getType());
        member.setJoin_filed(joinField);
        if (!type.equals(MemberType.GrandParent)) {
            if (StringUtils.isEmpty(parentId)) {
                throw new IllegalStateException("parentId cannot be null");
            }
            joinField.setParentId(parentId);
        }
        String source = JSONUtil.toJsonStr(member);
        log.debug("source: " + source);
        IndexRequest indexRequest = new IndexRequest("my-index-join_family").id(id).source(source, XContentType.JSON)
                .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        if (!type.equals(MemberType.GrandParent)) {
            indexRequest.routing(parentId);
        }
        try {
            IndexResponse out = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            log.debug(out.getId());
        } catch (IOException e) {
            log.error("", e);
        }
         return id;
    }

    @Override
    public String addParent(String grandPaId, String name) {
     return    this.addFamilyMember(grandPaId, name, MemberType.Parent);
    }

    @Override
    public String addChild(String parentId, String name) {
     return   this.addFamilyMember(parentId, name, MemberType.Child);
    }


    @Override
    public String addGrandPa(String name) {
      return   this.addFamilyMember(null, name, MemberType.GrandParent);
    }

    @Override
    public JoinFamily getFamilyTree(String grandPaId) throws IOException {

        JoinFamily grandPa = this.findFamilyMemberById(grandPaId);
        List<JoinFamily> list = this.getChild(grandPaId,MemberType.Parent);
        list.forEach(pa -> {
            try {
                pa.setChild(this.getChild(pa.getMy_id(),MemberType.Child));
            } catch (IOException e) {
                log.error("", e);
            }
        });
        grandPa.setChild(list);
        return grandPa;

    }

    @Override
    public JoinFamily findFamilyMemberById(String id) throws IOException {
        JoinFamily member=null;
        SearchRequest search= new SearchRequest("my-index-join_family");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder bool= QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery("my_id", id));
        searchSourceBuilder.query(bool);
        search.source(searchSourceBuilder);
        SearchResponse response=restHighLevelClient.search(search, RequestOptions.DEFAULT);
        SearchHit[] hit=response.getHits().getHits();
        if (hit.length>0){
            String source = hit[0].getSourceAsString();
            log.debug(source);
            member= JSONUtil.toBean(source,JoinFamily.class);
        }
        return member;
    }

    private List<JoinFamily> getChild(String grandPaId, MemberType type) throws IOException {
        List<JoinFamily> list = new ArrayList<>();
        SearchRequest search = new SearchRequest("my-index-join_family");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        ParentIdQueryBuilder build = JoinQueryBuilders.parentId(type.getType(), grandPaId);
        searchSourceBuilder.query(build);
        search.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(search, RequestOptions.DEFAULT);
        response.getHits().forEach(hi ->{
            String source = hi.getSourceAsString();
            log.debug(source);
            JoinFamily meber=JSONUtil.toBean(source,JoinFamily.class);
            list.add(meber);
        });
        return list;
    }

    @Override
    public List<ChildCount> getChildCountAndName( ) throws IOException {
        List<ChildCount> list = new LinkedList<>();
        SearchRequest search = new SearchRequest("my-index-join_family");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("join_filed", MemberType.Parent.getType()));
        AggregationBuilder build = AggregationBuilders.terms("parent_agg").field("currentType")
                .subAggregation(JoinAggregationBuilders.children("child_agg", "child")
                        .subAggregation(AggregationBuilders.scriptedMetric("child_name")
                                .initScript(new Script("state.transactions = []"))
                                .mapScript(new Script("state.transactions.add(doc.name)"))
                                .combineScript(new Script("String profit =''; for (t in state.transactions) { profit += t } return profit"))
                                .reduceScript(new Script("String profit = ''; for (a in states) { profit += a } return profit")))
                );
        searchSourceBuilder.aggregation(build);
        search.source(searchSourceBuilder);
        SearchResponse response = restHighLevelClient.search(search, RequestOptions.DEFAULT);
        Map<String, Aggregation> map = response.getAggregations().getAsMap();
        Terms terms = (Terms) map.get("parent_agg");
        terms.getBuckets().forEach(bucket -> {
            ChildCount count = new ChildCount();
            count.setParentName(bucket.getKeyAsString());
//            System.out.println(bucket.getKeyAsString());
            Map<String, Aggregation> subMap = bucket.getAggregations().getAsMap();
            Children children = (Children) subMap.get("child_agg");
            count.setChildCount(children.getDocCount());
//            System.out.println("childCount " + children.getDocCount());
            Map<String, Aggregation> subSubMap = children.getAggregations().getAsMap();
            ScriptedMetric metric = (ScriptedMetric) subSubMap.get("child_name");
            count.setChildren((String) metric.aggregation());
            list.add(count);
//            System.out.println("childName " + metric.aggregation());
        });
        return list;

    }
}
